<?php
/**
*
*   TODO getCurrentDate, compare: compare(date1,date2=current) <=> 0

*/

class Yz_Date
    extends Yz_Type
{
    const FORMAT_DATE_TEXT = 0;
    const FORMAT_DATE_NUM = 1;
    const FORMAT_DATE_LONG = 2;

    /**
     *  @var int|null
     */
    protected $year;
    /**
     *  @var int|null
     */
    protected $month;
    /**
     *  @var int|null
     */
    protected $day;

    protected static $MONTH_NAMES = array(
        1 => 'January',
        2 => 'February',
        3 => 'March',
        4 => 'April',
        5 => 'May',
        6 => 'June',
        7 => 'July',
        8 => 'August',
        9 => 'September',
        10 => 'October',
        11 => 'November',
        12 => 'December',
    );

    protected static $DATE_FORMATS = array(
        0 => '%{monthNameAndDay} %{year}',
        1 => '%{monthAndDay} %{year}',
        2 => '%{year}.%{month}.%{day}',
    );

    protected static $expects = array(
        'test'                => 'valid date',
    );


    /*************************
     *      Public methods   *
     *************************/


    /**
     *   @param mixed value
     */
    public function __construct( $value=null )
    {
        if (null !== $value) {
            $this->assign( $this::test($value) );
        }
    } // __construct


    public function __toString()
    {
        if ($this->isEmpty()) {
            return "";#Object(Yz_Date)[empty]";
        }
        return sprintf(
            '%d-%02d-%02d',
            $this->year,
            $this->month,
            $this->day
        );
    }

    /**
    *   get the actual value of Yz_Date instance as array [y,m,d] or null
    *
    *   @return null|array
    */
    public function getValue()
    {
        if ($this->isEmpty()) {
            return null;
        }
        return array(
            $this->year,
            $this->month,
            $this->day
        );
    } // getValue

    /**
    *   get the actual value of Yz_Date instance as array [y,m,d]
    *
    *   @return array
    */
    public function toArray()
    {
        if ($this->isEmpty()) {
            return array();
        }
        return array(
            $this->year,
            $this->month,
            $this->day
        );
    } // toArray



    /** isEmpty()
    *   @return bool
    */
    public function isEmpty()
    {
        return null === $this->year;
    } // isEmpty


    /** create a Date object from array(0: year, 1: month, 2:day)
     *  @param array-type
     *  @return Yz_Date
     */
    public static function fromArray($array)
    {
        $array = static::parseArray($array);
        $date = new static;
        return $date->assign($array);
    }


    /** create a Date object from array(0: year, 1: month, 2:day)
     *  @param array-type
     *  @return Yz_Date
     */
    public static function fromString($str)
    {
        $array = static::parseString($str);
        $date = new static;
        return $date->assign($array);
    }


    /** create a Date object from array(0: year, 1: month, 2:day)
     *  @param array-type
     *  @return Yz_Date
     */
    public static function fromTimestamp($time)
    {
        $array = static::parseTimestamp($time);
        $date = new static;
        return $date->assign($array);
    }




    /**
    *   @param scalar | array(3)[y,m,d]
    *   @accept:
    *       - string YY[YY]-MM-DD
    *       - string DD.MM.YY[YY]
    *       - string ????MMDD
    *       - integer MMDD ( 101 : 1231 )
    *       - integer YYMMDD ( YY0101 : 1231, YY is 20YY )
    *       - integer YYYYMMDD ( 10000101 : 99991231 )
    *       - integer UNIX_TIMESTAMP ( 99991232 .... )
    *   @return array[y,m,d]|null
    */
    public static function valueOf( $value )
    {
        switch ( true ) {
            case $value instanceof Yz_Date:
                $ovalue = $value->getValue();
                break;
            case is_string( $value ):
                $ovalue = static::parseString($value);
                break;
            case is_int( $value ):
                $ovalue = static::parseTimestamp($value);
                break;
            case is_array( $value ):
            case $value instanceof ArrayAccess:
                $ovalue = static::parseArray($value);
                break;
            default:
                $ovalue = null;
        }
        return $ovalue;
    } // valueOf


    /**
     *
     */
    public static function getElapsedTime()
    {
        return  microtime(true) - YZ_TIME;
    } // getElapsedTime


    public static function formatDate( $date, $formatNr=null )
    {
        $date = new static( $date );

        if ( null === $formatNr ) {
            $formatNr = self::FORMAT_DATE_TEXT;
        }
        if ( isset( static::$DATE_FORMATS[$formatNr] ) ) {
            $format = static::$DATE_FORMATS[$formatNr];
        } else {
            $format = static::$DATE_FORMATS[0];
        }

        $out = preg_replace_callback( '~%\{(\w+)\}~',
            function( $matches ) use ( $date ) {
                $var = $matches[1];
                return $date->{'get'.$var}();
            },
            $format
        );

        return $out;
    } // formatDate

    public function getMonth()
    {
        return $this->month;
    }

    public function getMonthName()
    {
        if ($this->isEmpty()) {
            return null;
        }
        $monthEn = $this->getEnglishMonthName();
        $str = I18N::t( $monthEn );
        return $str;
    }

    public function getMonthNameAndDay()
    {
        if ($this->isEmpty()) {
            return null;
        }
        $monthEn = $this->getEnglishMonthName();
        $str = I18N::t( $monthEn . ' %{day}' );
        return str_replace( '%{day}', $this->getDay(), $str );
    }
    public function getDay()
    {
        return $this->day;
    }
    public function getYear()
    {
        return $this->year;
    }
    public function getTimestamp()
    {
        if ($this->isEmpty()) {
            return null;
        }
        return mktime( 0, 0, 0, $this->month, $this->day, $this->year );
    }

    protected function getEnglishMonthName()
    {
        return self::$MONTH_NAMES[ $this->month ];
    }



    /**
     *  @todo what if the month/day of Date doesn't exist in the year being set?
     *
     */
    public function setYear($year)
    {
        $year = Yz_Int::testNotNegative($year);
        if ($year > 9999) {
            throw new Yz_Exception_InvalidArgument();
        }
        $this->year = $year;
        return $this;
    }

    /**
     *  @todo what if the day of Date doesn't exist in the month being set?
     *
     */
    public function setMonth($month)
    {
        $month = Yz_Int::testNotNegative($month);
        if ($month > 12) {
            throw new Yz_Exception_InvalidArgument();
        }
        $this->month = $month;
        return $this;
    }

    public function setDay($day)
    {
        $day = Yz_Int::testNotNegative($day);
        if (! checkdate($this->month, $day, $this->year)) {
            throw new Yz_Exception_InvalidArgument();
        }
        $this->day = $day;
        return $this;
    }


    /**
     *
     *  @return array[0: year, 1: month, 2: day]|null
     */
    protected static function parseString($value)
    {
        if ( preg_match( '~^\d{5,8}$~', $value ) ) {
            $d = substr($value, -2);
            $m = substr($value, -4, 2);
            $y = substr($value, 0, -4);
        } else if ( preg_match( '~^(\d{2,4})[-/]([01]?\d)[-/]([0123]?\d)$~', $value, $match ) ) {
            list( $v, $y, $m, $d ) = $match;
        } else if ( preg_match( '~^([0123]?\d).([01]?\d).(\d{2,4})$~', $value, $match ) ) {
            list( $v, $d, $m, $y ) = $match;
        } else {
            return null;
        }
        if ( strlen($y) < 4 ) {
            $y += 2000;
        }
        if (checkdate($m, $d, $y)) {
            return array($y, $m, $d);
        }
        return null;
    }

    /**
     *
     *  @return array[0: year, 1: month, 2: day]|null
     */
    protected static function parseArray($array)
    {
        debug();
        $val = Yz_Array::valueOf($array);
        if (null !== $val && 3 === count($val)) {
            if (checkdate($val[1], $val[2], $val[0])) {
                return $val;
            }
        }
        return null;
    }

    /**
     *
     *  @return array[0: year, 1: month, 2: day]|null
     */
    protected static function parseTimestamp($time)
    {
        debug();
        if ( $time <= 0 ) {
            return null;
        }
        if ( $time <= 99991231 ) { // timestamp
            return static::parseString( $time );
        }
        return explode('-', date('Y-m-d', $time));
    }



    /** test if the index exists
     *  @param string index (after adjustIndex)
     *  @return bool
     */
    protected function indexExists( $index )
    {
        switch (strtolower($index)) {
            case 'day':
            case 'month':
            case 'year':
                return true;
        }
        return false;
    }

    /** the default getter is called after index verification
     *  @param string index
     *  @return mixed|null
     */
    protected function defaultGetter( $index )
    {
        if ($this->indexExists( $index )) {
            return $this->$index;
        }
        return null;
    }

    /** the default setter is called after index verification
     *  @param string index
     *  @return chainable
     */
    protected function defaultSetter( $index, $value )
    {
        if ($this->indexExists( $index )) {
            if ($this->isEmpty()) {
                throw new Yz_Exception("Failed to set $index on empty date");
            }
            return $this->{'set'.$index}($value);
        }
        return $this;
    }

    protected function assign($array)
    {
        list($y, $m, $d) = $array;
        $this->year = $y;
        $this->month= $m;
        $this->day  = $d;
        return $this;
    }

} // class Yz_Date

